import moment from 'moment';
import { Alert } from 'react-native';
import { getServiceId, setServiceId, getCharacterId, setCharacterId, getPrinterAddess } from './clientStorage';
import bluetoothSerial from 'react-native-bluetooth-serial';
import { BleManager } from 'react-native-ble-plx';
import { isIOS, toast, isLaundryOrder } from './utils';
import { Buffer } from 'buffer';

const ble = new BleManager();

function listDevicesAndroid() {
  return bluetoothSerial.list()
    .then(result => result.map(item => ({address:item.address,name: item.name})));
}

export function listDevicesIOS() {
  return new Promise((resolve, reject) => {
    let results = [];

    ble.startDeviceScan(null, null, (error, device) => {
      if (device&&!results.find(item => item.address===device.id)) {
        results.push({address: device.id, name: device.name, ...device});
      }
    });

    setTimeout(() => {
      ble.stopDeviceScan();
      resolve(results);
    }, 10000);
  });
}

export function listDevices() {
  if (isIOS()) {
    return  listDevicesIOS();
  } else {
    return listDevicesAndroid();
  }
}

// ASCII only
function stringToBytes(string) {
  var array = new Uint8Array(string.length);
  for (var i = 0, l = string.length; i < l; i++) {
    array[i] = string.charCodeAt(i);
  }
  return array.buffer;
}

function connect(address) {
  return bluetoothSerial.isConnected()
    .then(connected => !connected&&bluetoothSerial.connect(address));
}

function connectIOS(address) {
  return ble.connectToDevice(address)
    .then(device => device.discoverAllServicesAndCharacteristics())
    .then(device => device.services())
    .then(services => Promise.all(services.map(service => service.characteristics())))
    .then(characteristics => {
      let foundItem;
      characteristics.map(items => {
        items.map(item => {
          if (item.isWritableWithoutResponse||item.isWritableWithResponse) {
            foundItem = item;
          }
        });
      });
      
      return foundItem;
    });
}

export function disconnect() {
  if (isIOS()) {

  } else {
    return bluetoothSerial.disconnect();
  }
}

function disconnectIOS(address) {
  return ble.cancelDeviceConnection(address);
}

function write(content) {
  return bluetoothSerial.write(content);
}

function flush() {
  return bluetoothSerial.clear();
}

function center(text, total) {
  let padding = '';
  if (text.length <= total - 2) {
    padding = parseInt((total - text.length) / 2);
    padding = Array.apply(null, Array(padding)).map(() => ' ').join('');
  }
  return `${padding}${text}`;
}

function end(text, total) {
  let padding = '';
  if (text.length < total) {
    padding = parseInt(total - text.length);
    padding = Array.apply(null, Array(padding)).map(() => ' ').join('');
  }
  return `${padding}${text}`;
}

function spaceBetween(left='', right='', total) {
  let leftLength = left.length;
  let rightLength = right.length;
  let middle = 0;
  if (total - leftLength - rightLength > 0) {
    middle = total - leftLength - rightLength;
  }
  middle = Array.apply(null, Array(middle)).map(() => ' ').join('');
  return `${left}${middle}${right}`;
}

function divider() {
  return '----------------';
}

function dividerLong() {
  return '--------------------------------';
}

function washType(type) {
  switch(type) {
    case 'wash': return 'Wash&Iron';
    case 'dry': return 'Dry&Iron';
    case 'iron': return 'Iron only';
    case 'flower': return '';
    case 'product': return '';
  }
}

function bigFont() {
  var data = new Uint8Array(5);
  data[0] = 0x1b;
  data[1] = 0x40;
  data[2] = 0x1d;
  data[3] = 0x21;
  data[4] = 0x11;
  return String.fromCharCode.apply(null, data);
}

function normalFont() {
  var data = new Uint8Array(5);
  data[0] = 0x1b;
  data[1] = 0x40;
  data[2] = 0x1d;
  data[3] = 0x21;
  data[4] = 0x00;
  return String.fromCharCode.apply(null, data);
}

const codeMap = {
  '0': 0x30,
  '1': 0x31,
  '2': 0x32,
  '3': 0x33,
  '4': 0x34,
  '5': 0x35,
  '6': 0x36,
  '7': 0x37,
  '8': 0x38,
  '9': 0x39,
};

function barCode(code) {
  let printCode = `${Array.apply(null, Array(6-code.length)).map(() => 0).join('')}${code}`;
  var data = new Uint8Array(12);
  data[0] = 0x1d;
  data[1] = 0x6b;
  data[2] = 0x47;
  data[3] = 0x08;
  data[4] = 0x41;
  data[5] = 0x32;
  data[6] = 0x33;
  data[7] = 0x34;
  data[8] = 0x35;
  data[9] = 0x36;
  data[10] = 0x30;
  data[11] = 0x41;

  for (let i = 0; i < printCode.length; i++) {
    data[5+i] = codeMap[printCode[i]];
  }
  
  return String.fromCharCode.apply(null, data);
}

function printOrderAndroid({type, userRef, displayId, printBrand='KnocKnock', pickup, delivery,
  pricing, estDate, warningNotes}, {firstName, lastName}, address) {
  let totalQuantity = 0;
  const ironItems = pricing.details.filter(item => !item.special&&(item.quantityType==='iron'));
  const washItems = pricing.details.filter(item => !item.special&&(item.quantityType==='wash'));
  const dryItems = pricing.details.filter(item => !item.special&&(item.quantityType==='dry'));
  const specialItems = pricing.details.filter(item => item.special);
  const rejectItems = pricing.details.filter(item => item.examptQuantity>0);

  return connect(address)
    .then(() => write(bigFont()))
    .then(() => flush())
    .then(() => Promise.all([
      write(`\n\n${center(printBrand, 16)}\n\n`),
      write(`\n#${displayId}${delivery.express?' EXP':''}${type==='premiumlaundry'?' PRE':''}${type==='loadwash'?' LW':''}`),
      write(`\n${delivery.district} [${delivery.postalCode.slice(0,2)}]`),
      write(`\n${userRef.firstName}`),
      isLaundryOrder(type)&&write(`\n${spaceBetween('Pickup:', moment.utc(new Date(pickup.date)).format('DD/MM'), 16)}`),
      isLaundryOrder(type)&&write(`\n${spaceBetween('EST:', estDate, 16)}`),
      warningNotes&&warningNotes.length>0&&write(`\n${divider()}\n`),
      warningNotes&&warningNotes.length>0&&write(`\nWARNING:\n${warningNotes.join('; ')}\n`),
      specialItems.length>0&&write(`\n${divider()}\n`),
      ...specialItems.map(({description, quantity}) => {
        return write(`\n${description} x ${quantity}`);
      }),
      specialItems.length>0&&write(`\n`),
      rejectItems.length>0&&write(`\n${divider()}\n[REJECT ITEMS]:\n`),
      ...rejectItems.map(({productName, examptQuantity}) => {
        return write(`\n${productName} x ${examptQuantity}`);
      }),
      rejectItems.length>0&&write(`\n`),
      (ironItems.length>0)&&write(`\n${divider()}`),
      ...ironItems.map(({price, description, quantity, examptQuantity=0, quantityType}) => {
        totalQuantity += (quantity-examptQuantity);
        return write(`\n${description}\n${end(`x ${quantity-examptQuantity}`, 16)}`);
      }),
      (washItems.length>0)&&write(`\n${divider()}`),
      ...washItems.map(({price, description, quantity, examptQuantity=0, quantityType}) => {
        totalQuantity += (quantity-examptQuantity);
        return write(`\n${description}\n${end(`x ${quantity-examptQuantity}`, 16)}`);
      }),
      (dryItems.length>0)&&write(`\n${divider()}`),
      ...dryItems.map(({price, description, quantity, examptQuantity=0, quantityType}) => {
        totalQuantity += (quantity-examptQuantity);
        return write(`\n${description}\n${end(`x ${quantity-examptQuantity}`, 16)}`);
      }),
      write(`\n${divider()}`),
      write(`\n${spaceBetween('Total', `${totalQuantity}`, 16)}\n\n`),
    ]))
    .then(() => flush())
    .then(() => write(normalFont()))
    .then(() => flush())
    .then(() => write(barCode(`${displayId}`)))
    .then(() => flush())
    .then(() => write(`\n\n\n`))
    .catch(error => {
      Alert.alert(
        'Printer Error',
        JSON.stringify(error),
        [
          {text: 'OK'},
        ],
        { cancelable: true }
      );
    });
}

function printOrderIOS({type, userRef, displayId, printBrand='KnocKnock', pickup, delivery,
  pricing, estDate, warningNotes}, {firstName, lastName}, address) {
  return connectIOS(address)
    .then(characteristic => {
      toast('Printering...');

      function write(content) {
        if (characteristic.isWritableWithResponse) {
          return characteristic.writeWithResponse(Buffer.from(content, 'utf8').toString('base64'));
        } else {
          return characteristic.writeWithoutResponse(Buffer.from(content, 'utf8').toString('base64'));
        }
      }

      let totalQuantity = 0;

      return Promise.resolve(write(bigFont()))
        .then(() => write(`\n\n${center(printBrand, 16)}\n`))
        .then(() => write(`\n#${displayId}${delivery.express?' EXP':''}${type==='premiumlaundry'?' PRE':''}${type==='loadwash'?' LW':''}`))
        .then(() => write(`\n${delivery.district} [${delivery.postalCode.slice(0,2)}]`))
        .then(() => write(`\n${userRef.firstName}`))
        .then(() => isLaundryOrder(type)&&write(`\n${spaceBetween('Pickup:', moment.utc(new Date(pickup.date)).format('DD/MM'), 16)}`))
        .then(() => isLaundryOrder(type)&&write(`\n${spaceBetween('EST:', estDate, 16)}`))
        .then(() => write(`\n${divider()}`))
        .then(() => Promise.all(pricing.details.filter(item => item.productId).map(({price, description, quantity, examptQuantity=0, quantityType}) => {
          totalQuantity += (quantity-examptQuantity);
          return Promise.all([
              write(`\n${description}`),
              write(` ${washType(quantityType)}`),
              write(`\n${end(`x ${quantity-examptQuantity}`, 16)}`)
            ]);
        })))
        .then(() => write(`\n${divider()}`))
        .then(() => write(`\n${spaceBetween('Total', `${totalQuantity}`, 16)}\n\n`))
        .then(() => write(normalFont()))
        .then(() => write(barCode(`${displayId}`)))
        .then(() => write('\n\n\n'))
        .catch(error => {
          Alert.alert(
            'Printer Error, Try Again',
            JSON.stringify(error),
            [
              {text: 'OK'},
            ],
            { cancelable: true }
          );
        });
    })
}

export function printOrder(order, worker) {
  return getPrinterAddess()
    .then(address => {
      if (!address) return toast(`Printer havn't configured, go to Printer tab in the home page to configure printer`);
    
      toast('Printing...');
      if (isIOS()) {
        return printOrderIOS(order, worker, address);
      } else {
        return printOrderAndroid(order, worker, address);
      }
    })
}

export function printReceipt(orders, {firstName, lastName}, chinese) {
  return getPrinterAddess()
    .then(address => {
      if (!address) {
        return toast(chinese?`您还没有选择打印机，请到'我'页面扫描并选择打印机`:`Printer havn't selected, please scan and select printer at 'Me' page`);
      }

      toast(chinese?'正在连接打印机...':'Contacting printer...');

      return Promise.resolve(isIOS()?connectIOS(address):connect(address))
        .then(characteristic => {
          toast(chinese?'打印中...':'Printering...');

          function write(content) {
            if (isIOS()) {
              if (characteristic.isWritableWithResponse) {
                return characteristic.writeWithResponse(Buffer.from(content, 'utf8').toString('base64'));
              } else {
                return characteristic.writeWithoutResponse(Buffer.from(content, 'utf8').toString('base64'));
              }
            } else {
              return bluetoothSerial.write(content);
            }
          }

          let totalPrice = 0;

          return Promise.resolve(write(bigFont()))
            .then(() => write(`\n\n${center('KnocKnock', 16)}\n`))
            .then(() => write(normalFont()))
            .then(() => write(`\n${spaceBetween('By:', `${firstName||''} ${lastName||''}`, 32)}`))
            .then(() => write(`\n${spaceBetween('Date:', moment().format('HH:mm MMM Do YYYY'), 32)}\n`))
            .then(() => Promise.all(orders.map(({displayId, pricing, userRef}) => {
              const details = pricing.details.filter((item, index) => item.productId&&(pricing.details.findIndex(detail=>detail.productName===item.productName)===index));

              return Promise.all([
                  write(`\n\n${spaceBetween(`${userRef.firstName}`, `#${displayId}`, 32)}`),
                  write(`\n${dividerLong()}`),
                  write(`\n${details.map(detail => `${detail.productName} x ${pricing.details.filter(item=>item.productName===detail.productName).map(item=>item.quantity-(item.examptQuantity||0)).reduce((a,b)=>a+b)}`).join('\n')}`),
                ]);
            })))
            .then(() => write('\n\n\n'))
            .then(() => write(`\n${dividerLong()}\n`))
            .then(() => write('\n\n\n'));
        });
    });
}


